/*
 * PySL:  Use S-Lang from Python (and vice versa)
 * Copyright (C) 2005,2006 Christopher J. Stawarz <chris@pseudogreen.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */



#include "libpysl.h"


/* Pointers to needed objects */
static PyObject *SLPy_Py_Main_Module = NULL;
static PyObject *SLPy_Py_Main_Module_Dict = NULL;
static SLang_MMT_Type *SLPy_Py_Main_Module_MMT = NULL;
static PyObject *SLPy_PY_KW = NULL;
static SLang_MMT_Type *SLPy_PY_KW_MMT = NULL;
static SLang_MMT_Type *SLPy_Py2SL_Error_Map_MMT = NULL;
static SLang_MMT_Type *SLPy_SL2Py_Error_Map_MMT = NULL;



/******************************************************************************
 *
 * Public module functions
 *
 *****************************************************************************/



static void
slpy_import(void)
{
  PyObject *module = NULL;
  PyObject *name_obj = NULL;
  char *name = NULL;
  char *dot = NULL;

  if (-1 == SLang_pop_slstring(&name)) {
    SLang_verror(SL_Usage_Error, "Usage: py_import(string name)");
    return;
  }

  if (NULL == (dot = strchr(name, '.')))
    name_obj = PyString_FromString(name);
  else
    name_obj = PyString_FromStringAndSize(name, (Py_ssize_t)(dot - name));

  if (NULL == name_obj ||
      NULL == (module = PyImport_ImportModuleEx(name, SLPy_Py_Main_Module_Dict,
						SLPy_Py_Main_Module_Dict,
						NULL)) ||
      -1 == PyObject_SetAttr(SLPy_Py_Main_Module, name_obj, module))
    libpysl_python_to_slang_error();

  SLang_free_slstring(name);
  Py_XDECREF(module);
  Py_XDECREF(name_obj);
}


static void
slpy_eval(void)
{
  PyObject *globals = SLPy_Py_Main_Module_Dict;
  PyObject *locals = NULL;
  PyObject *result = NULL;
  SLang_MMT_Type *globals_mmt = NULL;
  SLang_MMT_Type *locals_mmt = NULL;
  char *str = NULL;

  int status = 0;

  switch (SLang_Num_Function_Args) {
  case 3:
    status = slpy_pop_pyobject(&locals, &locals_mmt);
  case 2:
    if (!status)
      status = slpy_pop_pyobject(&globals, &globals_mmt);
  case 1:
    if (!status)
      status = SLang_pop_slstring(&str);
    break;
  default:
    status = -1;
    break;
  }

  if (0 != status) {
    SLang_verror(SL_Usage_Error,
		 "Usage: [result =] py_eval(string pyexpr[, "
		 "PyObject globals[, PyObject locals]])");
    goto error;
  }

  if (NULL == locals)
    locals = globals;

  if (NULL == (result = PyRun_String(str, Py_eval_input, globals, locals)) ||
      -1 == libpysl_push_pyobject(result)) {
    libpysl_python_to_slang_error();
    goto error;
  }

 error:
  SLang_free_mmt(globals_mmt);
  SLang_free_mmt(locals_mmt);
  Py_XDECREF(result);
  SLang_free_slstring(str);
}


static void
slpy_exec(void)
{
  PyObject *globals = SLPy_Py_Main_Module_Dict;
  PyObject *locals = NULL;
  PyObject *result = NULL;
  SLang_MMT_Type *globals_mmt = NULL;
  SLang_MMT_Type *locals_mmt = NULL;
  char *str = NULL;

  int status = 0;

  switch (SLang_Num_Function_Args) {
  case 3:
    status = slpy_pop_pyobject(&locals, &locals_mmt);
  case 2:
    if (!status)
      status = slpy_pop_pyobject(&globals, &globals_mmt);
  case 1:
    if (!status)
      status = SLang_pop_slstring(&str);
    break;
  default:
    status = -1;
    break;
  }

  if (0 != status) {
    SLang_verror(SL_Usage_Error,
		 "Usage: py_exec(string pycode[, "
		 "PyObject globals[, PyObject locals]])");
    goto error;
  }

  if (NULL == locals)
    locals = globals;

  if (NULL == (result = PyRun_String(str, Py_file_input, globals, locals))) {
    libpysl_python_to_slang_error();
    goto error;
  }

 error:
  SLang_free_mmt(globals_mmt);
  SLang_free_mmt(locals_mmt);
  Py_XDECREF(result);
  SLang_free_slstring(str);
}


static void
slpy_do_call(const char *usage, int raw_return)
{
  PyObject *obj = NULL;
  SLang_MMT_Type *mmt = NULL;
  PyObject *args = NULL;
  PyObject *kwargs = NULL;
  PyObject *item = NULL;
  PyObject *key = NULL;
  PyObject *retval = NULL;
  int nargs;
  int py_kw_index = -1;
  int i;

  nargs = SLang_Num_Function_Args;

  if (nargs < 1 ||
      -1 == SLroll_stack(-(nargs--)) ||
      -1 == slpy_pop_pyobject(&obj, &mmt)) {
    SLang_verror(SL_Usage_Error, "Usage: %s", usage);
    goto error;
  }

  if (NULL == (args = PyTuple_New(nargs))) {
    libpysl_python_to_slang_error();
    goto error;
  }

  for (i = nargs - 1; i >= 0; i--) {
    if (NULL == (item = libpysl_pop_pyobject())) {
      libpysl_python_to_slang_error();
      goto error;
    }

    /* Even if item is PY_KW, we still want to insert it into args, since
     * PY_KW could appear more than once in the argument list.  Since we
     * step through the arguments in reverse order, py_kw_index will point
     * to the first occurrence of PY_KW. */
    if (item == SLPy_PY_KW)
      py_kw_index = i;

    if (PyTuple_SetItem(args, i, item)) {
      libpysl_python_to_slang_error();
      goto error;
    }
  }

  /* Handle keyword arguments */
  if (py_kw_index >= 0) {
    if (0 != (nargs - 1 - py_kw_index) % 2) {
      SLang_verror(SL_StackUnderflow_Error,
		   "missing keyword value after PY_KW");
      goto error;
    }

    if (NULL == (kwargs = PyDict_New())) {
      libpysl_python_to_slang_error();
      goto error;
    }

    for (i = py_kw_index + 1; i < nargs; i += 2) {
      if (NULL == (key = PyTuple_GetItem(args, i)) ||
	  NULL == (item = PyTuple_GetItem(args, i+1)) ||
	  -1 == PyDict_SetItem(kwargs, key, item)) {
	libpysl_python_to_slang_error();
	goto error;
      }
    }

    if (-1 == _PyTuple_Resize(&args, py_kw_index)) {
      libpysl_python_to_slang_error();
      goto error;
    }
  }

  if (NULL == (retval = PyObject_Call(obj, args, kwargs))) {
    libpysl_python_to_slang_error();
    goto error;
  }

  if (!raw_return && (retval == Py_None)) {

    /* Return nothing */

  } else if (raw_return || !PyTuple_CheckExact(retval)) {

    if (-1 == libpysl_push_pyobject(retval)) {
      libpysl_python_to_slang_error();
      goto error;
    }

  } else {

    for (i = 0; i < PyTuple_Size(retval); i++) {
      if (NULL == (item = PyTuple_GetItem(retval, i)) ||
	  -1 == libpysl_push_pyobject(item)) {
	libpysl_python_to_slang_error();
	goto error;
      }
    }
  }

 error:
  SLang_free_mmt(mmt);
  Py_XDECREF(args);
  Py_XDECREF(kwargs);
  Py_XDECREF(retval);
}


static void
slpy_call(void)
{
  slpy_do_call("[result =] py_call(PyObject obj[, ...])", 0);
}


static void
sl_py_call(void)
{
  slpy_do_call("result = _py_call(PyObject obj[, ...])", 1);
}



/******************************************************************************
 *
 * Module initialization
 *
 *****************************************************************************/



static char *SLPy_Version_String = PACKAGE_VERSION;


static SLang_IConstant_Type SLPy_IConsts[] = {
  MAKE_ICONSTANT("_pysl_version", PACKAGE_VERSION_NUMBER),
  SLANG_END_ICONST_TABLE
};


static SLang_Intrin_Var_Type SLPy_IVars[] = {
  MAKE_VARIABLE("_pysl_version_string", &SLPy_Version_String,
		SLANG_STRING_TYPE, 1),
  SLANG_END_INTRIN_VAR_TABLE
};


static SLang_Intrin_Fun_Type SLPy_Funcs[] = {
  MAKE_INTRINSIC_0("py_import", slpy_import, SLANG_VOID_TYPE),
  MAKE_INTRINSIC_0("py_eval", slpy_eval, SLANG_VOID_TYPE),
  MAKE_INTRINSIC_0("py_exec", slpy_exec, SLANG_VOID_TYPE),
  MAKE_INTRINSIC_0("py_call", slpy_call, SLANG_VOID_TYPE),
  MAKE_INTRINSIC_0("_py_call", sl_py_call, SLANG_VOID_TYPE),
  SLANG_END_INTRIN_FUN_TABLE
};


static void
slpy_deinit_objects(void)
{
  SLang_free_mmt(SLPy_Py_Main_Module_MMT);
  SLPy_Py_Main_Module = NULL;		/* Borrowed reference */
  SLPy_Py_Main_Module_Dict = NULL;	/* Borrowed reference */
  SLPy_Py_Main_Module_MMT = NULL;

  Py_XDECREF(SLPy_PY_KW);
  SLang_free_mmt(SLPy_PY_KW_MMT);
  SLPy_PY_KW = NULL;
  SLPy_PY_KW_MMT = NULL;

  SLang_free_mmt(SLPy_Py2SL_Error_Map_MMT);
  SLang_free_mmt(SLPy_SL2Py_Error_Map_MMT);
  SLPy_Py2SL_Error_Map_MMT = NULL;
  SLPy_SL2Py_Error_Map_MMT = NULL;
}


static int
slpy_init_objects(void)
{
  PyObject *tmp = NULL;

  /* Free anything we may have created previously */
  slpy_deinit_objects();

  /*
   * __main__ module ("py" variable in S-Lang)
   */

  /* Borrowed reference.  Don't INCREF because slpy_allocate_pyobject()
   * will do so. */
  if (NULL == (SLPy_Py_Main_Module = PyImport_AddModule("__main__"))) {
    libpysl_python_to_slang_error();
    return -1;
  }

  /* Also a borrowed reference, but we don't need to INCREF because we'll
   * own a reference to __main__ via SLPy_Py_Main_Module_MMT. */
  if (NULL == (SLPy_Py_Main_Module_Dict =
	       PyModule_GetDict(SLPy_Py_Main_Module))) {
    libpysl_python_to_slang_error();
    return -1;
  }

  tmp = PyRun_String("from __builtin__ import *", Py_single_input,
		     SLPy_Py_Main_Module_Dict, NULL);
  Py_XDECREF(tmp);

  if (NULL == tmp) {
    libpysl_python_to_slang_error();
    return -1;
  }

  if (NULL == (SLPy_Py_Main_Module_MMT =
	       slpy_allocate_pyobject(SLPy_Py_Main_Module)))
    return -1;

  /*
   * Py_KW
   */

  if (NULL == (SLPy_PY_KW = PyCObject_FromVoidPtr(NULL, NULL))) {
    libpysl_python_to_slang_error();
    return -1;
  }

  if (NULL == (SLPy_PY_KW_MMT = slpy_allocate_pyobject(SLPy_PY_KW)))
    return -1;

  /*
   * Error maps
   */

  if (NULL == (SLPy_Py2SL_Error_Map_MMT = 
	       slpy_allocate_pyobject(LibPySL_Py2SL_Error_Map)))
    return -1;

  if (NULL == (SLPy_SL2Py_Error_Map_MMT = 
	       slpy_allocate_pyobject(LibPySL_SL2Py_Error_Map)))
    return -1;

  return 0;
}


SLANG_MODULE(pysl);
int
init_pysl_module_ns(char *ns_name)
{
  SLang_NameSpace_Type *ns = NULL;

  if (NULL == (ns = SLns_create_namespace(ns_name)))
    return -1;

  if (-1 == libpysl_init(0)) {
    libpysl_python_to_slang_error();
    return -1;
  }

  if ((-1 == SLns_add_iconstant_table(ns, SLPy_IConsts, NULL)) ||
      (-1 == SLns_add_intrin_var_table(ns, SLPy_IVars, NULL)) ||
      (-1 == SLns_add_intrin_fun_table(ns, SLPy_Funcs, "__pysl__")))
    return -1;

  if (-1 == slpy_init_objects())
    return -1;

  if (-1 == SLns_add_intrinsic_variable(ns, "py", &SLPy_Py_Main_Module_MMT,
					SLPy_PyObject_Type, 1) ||
      -1 == SLns_add_intrinsic_variable(ns, "PY_KW", &SLPy_PY_KW_MMT,
					SLPy_PyObject_Type, 1) ||
      -1 == SLns_add_intrinsic_variable(ns, "py2sl_errormap",
					&SLPy_Py2SL_Error_Map_MMT,
					SLPy_PyObject_Type, 1) ||
      -1 == SLns_add_intrinsic_variable(ns, "sl2py_errormap",
					&SLPy_SL2Py_Error_Map_MMT,
					SLPy_PyObject_Type, 1))
    return -1;

  return 0;
}


void
deinit_pysl_module(void)
{
  slpy_deinit_objects();
  libpysl_deinit();
}
